<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="utf-8" />
   
  <meta name="keywords" content="Java Python" />
   
  <meta name="description" content="From Zero to Hero" />
  
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
  <title>
    Netty-02-NIO实战及原理 |  朱酱酱的学习博客
  </title>
  <meta name="generator" content="hexo-theme-yilia-plus">
  
  <link rel="shortcut icon" href="/favicon.ico" />
  
  
<link rel="stylesheet" href="/css/style.css">

  
<script src="/js/pace.min.js"></script>


  

  

<link rel="alternate" href="/atom.xml" title="朱酱酱的学习博客" type="application/atom+xml">
</head>

</html>

<body>
  <div id="app">
    <main class="content">
      <section class="outer">
  <article id="post-Netty/Netty-02-NIO实战及原理" class="article article-type-post" itemscope
  itemprop="blogPost" data-scroll-reveal>

  <div class="article-inner">
    
    <header class="article-header">
       
<h1 class="article-title sea-center" style="border-left:0" itemprop="name">
  Netty-02-NIO实战及原理
</h1>
  

    </header>
    

    
    <div class="article-meta">
      <a href="/2020/10/01/Netty/Netty-02-NIO%E5%AE%9E%E6%88%98%E5%8F%8A%E5%8E%9F%E7%90%86/" class="article-date">
  <time datetime="2020-10-01T03:05:24.000Z" itemprop="datePublished">2020-10-01</time>
</a>
      
      
      
<div class="word_count">
    <span class="post-time">
        <span class="post-meta-item-icon">
            <i class="ri-quill-pen-line"></i>
            <span class="post-meta-item-text"> 字数统计:</span>
            <span class="post-count">7.4k字</span>
        </span>
    </span>

    <span class="post-time">
        &nbsp; | &nbsp;
        <span class="post-meta-item-icon">
            <i class="ri-book-open-line"></i>
            <span class="post-meta-item-text"> 阅读时长≈</span>
            <span class="post-count">31分钟</span>
        </span>
    </span>
</div>

      
    </div>
    

    
    
    <div class="tocbot"></div>





    

    <div class="article-entry" itemprop="articleBody">
      


      

      
      <h1 id="Netty-02-NIO实战及原理"><a href="#Netty-02-NIO实战及原理" class="headerlink" title="Netty-02-NIO实战及原理"></a>Netty-02-NIO实战及原理</h1><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><ul>
<li><p>Java NIO 全称 <code>java non-blocking IO</code>，是指 JDK 提供的新 API。从 JDK1.4 开始，Java 提供了一系列改进的输入/输出的新特性，被统称为 NIO(即 New IO)，是<strong>同步非阻塞</strong>的</p>
</li>
<li><p>NIO 相关类都被放在 <code>java.nio</code> 包及子包下，并且对原 <code>java.io</code> 包中的很多类进行改写。</p>
</li>
<li><p>NIO 有三大核心部分：<strong>Channel(通道)</strong>，<strong>Buffer(缓冲区)</strong>, <strong>Selector(选择器)</strong></p>
</li>
<li><p>NIO是 面向<strong>缓</strong>冲区 ，或者面向 <strong>块</strong> 编程的。数据读取到一个它稍后处理的缓冲区，需要时可在缓冲区中前后移动，这就增加了处理过程中的灵活性，使用它可以提供<strong>非阻塞</strong>式的高伸缩性网络</p>
</li>
<li><p><code>Java NIO</code>的非阻塞模式，使一个线程从某通道发送请求或者读取数据，但是它仅能得到目前可用的数据，如果目前没有数据可用时，就什么都不会获取，而<strong>不是保持线程阻塞</strong>，所以直至数据变的可以读取之前，该线程可以继续做其他的事情。 非阻塞写也是如此，一个线程请求写入一些数据到某通道，但不需要等待它完全写入，这个线程同时可以去做别的事情。</p>
</li>
<li><p>通俗理解：<code>NIO</code>是可以做到用一个线程来处理多个操作的。假设有<code>10000</code>个请求过来,根据实际情况，可以分配<code>50</code>或者<code>100</code>个线程来处理。不像之前的阻塞<code>IO</code>那样，非得分配<code>10000</code>个。</p>
</li>
<li><p><code>HTTP2.0</code>使用了多路复用的技术，做到同一个连接并发处理多个请求，而且并发请求的数量比<code>HTTP1.1</code>大了好几个数量级。</p>
</li>
</ul>
<a id="more"></a>



<h2 id="1-NIO三大核心概述"><a href="#1-NIO三大核心概述" class="headerlink" title="1. NIO三大核心概述"></a>1. NIO三大核心概述</h2><p><img src="http://zhuuu-bucket.oss-cn-beijing.aliyuncs.com/img/20201002/111307133.png" alt="mark"></p>
<p><strong>Buffer：</strong></p>
<ul>
<li>缓存数组，就是一个内存块，底层用数组实现</li>
<li>与<code>Channel</code>进行数据的读写。</li>
<li>数据的读取写入是通过<code>Buffer</code>, 这个和<code>BIO</code> 一样, 而<code>BIO</code> 中要么是输入流，或者是输出流, 不能双向，但是<code>NIO</code>的<code>Buffer</code> 是可以读也可以写, 需要 <code>flip</code> 方法切换。</li>
</ul>
<p><strong>Channel：</strong></p>
<ul>
<li>通信通道，每个客户端连接都会建立一个<code>Channel</code>通道</li>
<li>客户端直接与<code>Channel</code>进行通信，当客户端发送消息时，消息就流通到<code>Channel</code>里面，本地程序需要将<code>Channel</code>里面的数据存放在<code>Buffer</code>里面，才可以查看；当本地需要发送消息时，先把消息存在<code>Buffer</code>里面，再将<code>Buffer</code>里面的数据放入<code>Channel</code>，数据就流通到了客户端</li>
<li>总而言之：<code>Buffer</code>就是本地程序与<code>Channel</code>数据交换的一个中间媒介。</li>
</ul>
<p><strong>Selector</strong></p>
<p><img src="http://zhuuu-bucket.oss-cn-beijing.aliyuncs.com/img/20201002/111954021.png" alt="mark"></p>
<ul>
<li>NIO之所以是非阻塞的，关键在于它一个线程可以同时处理多个客户端的通信。而<code>Selector</code>就是它一个线程如何处理多个客户端通信的关键，一个<code>Selector</code>就对应一个线程</li>
<li>首先在创建与客户端连接的<code>Channel</code>时，应该调用 <code>Channel.register()</code>方法，将Channel注册到一个<code>Selector</code>上面。调用该方法后，会返回一个<code>SelectionKey</code>对象，该对象与<code>Channel</code>是一一对应的。而<code>Selector</code>则通过管理<code>SelectionKey</code>的集合间接的去管理各个<code>Channel</code>。示例图如下：</li>
</ul>
<p><img src="http://zhuuu-bucket.oss-cn-beijing.aliyuncs.com/img/20201002/112011816.png" alt="mark"></p>
<p><code>Selector</code>具体是如何管理这么多个通信的呢？这就引出了<strong>事件</strong>。</p>
<p><strong>事件、以及NIO的工作流程介绍</strong></p>
<ul>
<li><strong>事件：</strong>当将<code>Channel</code>绑定到<code>Selector</code>上面时，必须同时为该<code>Channel</code>声明一个监听该<code>Channel</code>的事件（由<code>Channel</code>和该<code>Channel的事件</code>一起组成了<code>SelectionKey</code>），并将<code>SelectionKey</code>加入到<code>Selector</code>的<code>Set</code>集合中去</li>
<li>当有客户端建立连接或者进行通信，会在对应的各个<code>Channel</code>中产生不同的事件。</li>
<li><code>Selector</code>会一直监听所有的事件，当他监听到某个<code>SelectionKey</code>中有事件产生时，会将所有产生事件的<code>SelectionKey</code>统一加入到一个集合中去</li>
<li>而我们则需要获取到这个集合，首先对集合中的各个<code>SelectionKey</code>进行判断，判断它产生的是什么事件，再根据不同的事件进行不同的处理。</li>
<li>在操作这个<code>SelectionKey</code>集合的时候，其实我们就是在一个线程里面对几个不同客户端的连接进行操作。具体的关系图如下：</li>
</ul>
<p><img src="http://zhuuu-bucket.oss-cn-beijing.aliyuncs.com/img/20201006/184806838.png" alt="mark"></p>
<h2 id="2-Buffer"><a href="#2-Buffer" class="headerlink" title="2. Buffer"></a>2. Buffer</h2><ul>
<li>缓冲区（Buffer）：缓冲区本质上是一个<strong>可以读写数据的内存块</strong>，可以理解成是一个容器对象（<strong>含数组</strong>），</li>
<li>该对象提供了一组方法，可以更轻松地使用内存块，缓冲区对象内置了一些机制，能够跟踪和记录缓冲区的状态变化情况。</li>
<li><code>Channel</code>提供从文件、网络读取数据的渠道，但是读取或写入的数据都必须经由<code>Buffer</code>。</li>
</ul>
<h3 id="2-1-Buffer类介绍"><a href="#2-1-Buffer类介绍" class="headerlink" title="2.1 Buffer类介绍"></a>2.1 Buffer类介绍</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="class"><span class="keyword">class</span> <span class="title">Buffer</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * The characteristics of Spliterators that traverse and split elements</span></span><br><span class="line"><span class="comment">     * maintained in Buffers.</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> SPLITERATOR_CHARACTERISTICS =</span><br><span class="line">        Spliterator.SIZED | Spliterator.SUBSIZED | Spliterator.ORDERED;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Invariants: mark &lt;= position &lt;= limit &lt;= capacity</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">int</span> mark = -<span class="number">1</span>;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">int</span> position = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">int</span> limit;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">int</span> capacity;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Used only by direct buffers</span></span><br><span class="line">    <span class="comment">// <span class="doctag">NOTE:</span> hoisted here for speed in JNI GetDirectBufferAddress</span></span><br><span class="line">    <span class="keyword">long</span> address;</span><br></pre></td></tr></table></figure>

<p><img src="http://zhuuu-bucket.oss-cn-beijing.aliyuncs.com/img/20201006/185018679.png" alt="mark"></p>
<ul>
<li><p>基类是<code>Buffer</code>抽象类</p>
</li>
<li><p>基类派生出基于基本数据类型的7个<code>xxxBuffer</code> 抽象类，没有<code>boolean</code>相关的<code>buffer</code>类。</p>
</li>
<li><p>除了<code>ByteBuffer</code>外，每个基本数据的抽象类 <code>xxxBuffer</code> 类下面都派生出转向 <code>ByteBuffer</code> 的类 <code>ByteBufferXxxAsBufferL</code> 和 <code>ByteBufferAsXxxBufferB</code>实现类；以及 <code>DirectXxxBufferU</code> 和 <code>DirectXxxBufferS</code> 和 <code>HeapXxxBuffer</code>==（具体实例对象类）==这五个类。</p>
</li>
<li><p>就只有抽象类<code>CharBuffer</code> 派生出了第六个类<code>StringCharBuffer</code>。</p>
</li>
<li><p><code>ByteBuffer</code>只派生出了 <code>HeapByteBuffer</code> 和 <code>MappedByteBufferR</code> 两个类</p>
</li>
</ul>
<p><img src="http://zhuuu-bucket.oss-cn-beijing.aliyuncs.com/img/20201006/185139008.png" alt="mark"></p>
<h3 id="2-2-Buffer-属性"><a href="#2-2-Buffer-属性" class="headerlink" title="2.2 Buffer 属性"></a>2.2 Buffer 属性</h3><table>
<thead>
<tr>
<th><strong>属性</strong></th>
<th><strong>描述</strong></th>
</tr>
</thead>
<tbody><tr>
<td>Capacity</td>
<td>容量，即可以容纳的最大数据量；在缓冲区创建时被设定并且不能改变</td>
</tr>
<tr>
<td>Limit</td>
<td>表示缓冲区的当前终点，不能对缓冲区超过极限的位置进行读写操作。且极限是可以修改的</td>
</tr>
<tr>
<td>Position</td>
<td>位置，下一个要被读或写的元素的索引，每次读写缓冲区数据时都会改变改值，为下次读写作准备</td>
</tr>
<tr>
<td>Mark</td>
<td>标记 ，一般不会主动修改，在<code>flip()</code>被调用后，mark就作废了。</td>
</tr>
</tbody></table>
<blockquote>
<p>mark &lt;= position &lt;= limit &lt;= capacity</p>
</blockquote>
<h3 id="2-3-简单使用"><a href="#2-3-简单使用" class="headerlink" title="2.3 简单使用"></a>2.3 简单使用</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">BasicBuffer</span> </span>&#123;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 1. 举例说明Buffer的使用</span></span><br><span class="line">        <span class="comment">// 创建一个IntBuffer,大小为5,即存放5个int类型</span></span><br><span class="line">        IntBuffer intBuffer = IntBuffer.allocate(<span class="number">5</span>);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 2. 向Buffer中存储数据</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; intBuffer.capacity(); i++) &#123;</span><br><span class="line">            intBuffer.put(i * <span class="number">2</span>);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">        <span class="comment">// 3. 从Buffer中读取数据</span></span><br><span class="line">        <span class="comment">// 将buffer进行读写切换（重要）</span></span><br><span class="line">        intBuffer.flip();</span><br><span class="line"></span><br><span class="line">        <span class="keyword">while</span> (intBuffer.hasRemaining())&#123;</span><br><span class="line">            System.out.println(intBuffer.get());</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<ul>
<li>Buffer<code>刚创建时，</code>capacity = 5<code>，固定不变。</code>limit<code>指针指向</code>5<code>，</code>position<code>指向</code>0<code>，</code>mark<code>指向</code>-1</li>
</ul>
<p><img src="http://zhuuu-bucket.oss-cn-beijing.aliyuncs.com/img/20201006/185326111.png" alt="mark"></p>
<ul>
<li>之后调用 <code>intBuffer.put</code>方法，向<code>buffer</code>中添加数据，会不断移动<code>position</code>指针，最后<code>position</code>变量会和<code>limit</code>指向相同。</li>
</ul>
<p><img src="http://zhuuu-bucket.oss-cn-beijing.aliyuncs.com/img/20201006/185351549.png" alt="mark"></p>
<ul>
<li>调用 <code>buffer.flip()</code>实际上是重置了<code>position</code>和<code>limit</code>两个变量，将<code>limit</code>放在<code>position</code>的位置，<code>position</code>放在<code>0</code>的位置。这里只是最后的<code>position</code>和<code>limit</code>位置相同，所以<code>flip</code>后<code>limit</code>位置没变。</li>
</ul>
<p><img src="http://zhuuu-bucket.oss-cn-beijing.aliyuncs.com/img/20201006/185407014.png" alt="mark"></p>
<ul>
<li>调用 <code>intBuffer.get()</code>实际上是不断移动<code>position</code>指针，直到它移动到<code>limit</code>的位置</li>
</ul>
<h3 id="2-4-Buffer-类方法"><a href="#2-4-Buffer-类方法" class="headerlink" title="2.4 Buffer 类方法"></a>2.4 Buffer 类方法</h3><h5 id="Buffer基类（抽象类）"><a href="#Buffer基类（抽象类）" class="headerlink" title="Buffer基类（抽象类）"></a>Buffer基类（抽象类）</h5><ul>
<li><code>public final int capacity();</code><ul>
<li>直接返回了此缓冲区的容量，<code>capacity</code></li>
</ul>
</li>
<li><code>public final int position();</code><ul>
<li>直接返回了此缓冲区指针的当前位置</li>
</ul>
</li>
<li><code>public final Buffer position(int newPosition);</code><ul>
<li>设置此缓冲区的位置，设置<code>position</code></li>
</ul>
</li>
<li><code>public final int limit();</code><ul>
<li>返回此缓冲区的限制</li>
</ul>
</li>
<li><code>public final Buffer limit(int newLimit);</code><ul>
<li>设置此缓冲区的限制，设置<code>limit</code></li>
</ul>
</li>
<li><code>public final Buffer clear();</code><ul>
<li>清除此缓冲区，即将各个标记恢复到初识状态， <code>position = 0;limit = capacity; mark = -1</code>，但是并没有删除数据。</li>
</ul>
</li>
<li><code>public final Buffer flip();</code><ul>
<li>反转此缓冲区， <code>limit = position;position = 0;mark = -1</code>。</li>
<li>当指定数据存放在缓冲区中后，<code>position</code>所指向的即为此缓冲区数据最后的位置。只有当数据大小和此缓冲区大小相同时，<code>position</code>才和<code>limit</code>的指向相同。</li>
<li><code>flip()</code>方法将<code>limit</code>置向<code>position</code>， <code>position</code>置<code>0</code>，那么从<code>position</code>读取数据到<code>limit</code>即为此缓冲区中所有的数据。</li>
</ul>
</li>
<li><code>public final boolean hasRemaining();</code><ul>
<li>告知当前位置和限制之间是否有元素。<code>return position &lt; limit;</code></li>
</ul>
</li>
<li><code>public abstract boolean isReadOnly();</code><ul>
<li>此方法为抽象方法，告知此缓冲区是否为只读缓冲区，具体实现在各个实现类中。</li>
</ul>
</li>
<li><code>public abstract boolean hasArray();</code><ul>
<li>告知此缓冲区是否具有可访问的底层实现数组</li>
</ul>
</li>
<li><code>public abstract Object array();</code><ul>
<li>返回此缓冲区的底层实现数组</li>
</ul>
</li>
</ul>
<h3 id="2-5-ByteBuffer的使用"><a href="#2-5-ByteBuffer的使用" class="headerlink" title="2.5 ByteBuffer的使用"></a>2.5 ByteBuffer的使用</h3><p>从前面可以看出来对于Java中的基本数据类型（<strong>boolean除外</strong>），都有一个<code>Buffer</code>类型与之对应，最常用的自然是<code>ByteBuffer</code>类（二进制数据），该类的主要方法如下：</p>
<ul>
<li><code>public static ByteBuffer allocateDirect(int capacity);</code><ul>
<li>创建直接缓冲区</li>
</ul>
</li>
<li><code>public static ByteBuffer allocate(int capacity) ;</code><ul>
<li>设置缓冲区的初识容量</li>
</ul>
</li>
<li><code>public abstract byte get();</code><ul>
<li>从当前位置<code>position</code>上<code>get</code>数据，获取之后，<code>position</code>会自动加<code>1</code></li>
</ul>
</li>
<li><code>public abstract byte get(int index);</code><ul>
<li>通过绝对位置获取数据。</li>
</ul>
</li>
<li><code>public abstract ByteBuffer put(byte b);</code><ul>
<li>从当前位置上添加，<code>put</code>之后，<code>position</code>会自动加<code>1</code></li>
</ul>
</li>
<li><code>public abstract ByteBuffer put(int index, byte b);</code><ul>
<li>从绝对位置上添加数据</li>
</ul>
</li>
<li><code>public abstract ByteBuffer putXxx(Xxx value [, int index]);</code><ul>
<li>从<code>position</code>当前位置插入元素。<code>Xxx</code>表示基本数据类型</li>
<li>此方法时类型化的 <code>put</code> 和 <code>get</code>，<code>put</code>放入的是什么数据类型，<code>get</code>就应该使用相应的数据类型来取出，否则可能有 <code>BufferUnderflowException</code> 异常。</li>
</ul>
</li>
</ul>
<p><strong>demo01</strong> :  <em>当取出的顺序和上面插入的数据类型的顺序不对时，就会抛出BufferUnderflowException异常</em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">ByteBuffer buf = ByteBuffer.allocate(<span class="number">64</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">//类型化方式放入数据</span></span><br><span class="line">buf.putInt(<span class="number">100</span>);</span><br><span class="line">buf.putLong(<span class="number">20</span>);</span><br><span class="line">buf.putChar(<span class="string">'上'</span>);</span><br><span class="line">buf.putShort((<span class="keyword">short</span>)<span class="number">44</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">//取出，当取出的顺序和上面插入的数据类型的顺序不对时，就会抛出BufferUnderflowException异常</span></span><br><span class="line">buf.flip();</span><br><span class="line">System.out.println(buf.getInt());</span><br><span class="line">System.out.println(buf.getLong());</span><br><span class="line">System.out.println(buf.getChar());</span><br><span class="line">System.out.println(buf.getShort());</span><br></pre></td></tr></table></figure>



<p><strong>demo02 : 可以将一个普通的Buffer转成只读的Buffer</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//创建一个Buffer</span></span><br><span class="line">ByteBuffer byteBuffer = ByteBuffer.allocate(<span class="number">64</span>);</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; <span class="number">64</span>; i++) &#123;</span><br><span class="line">    byteBuffer.put((<span class="keyword">byte</span>)i);</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//读取</span></span><br><span class="line">byteBuffer.flip();</span><br><span class="line"><span class="comment">//得到一个只读的Buffer</span></span><br><span class="line">ByteBuffer readOnlyBuffer = byteBuffer.asReadOnlyBuffer();</span><br><span class="line">System.out.println(readOnlyBuffer.getClass());</span><br><span class="line"><span class="comment">//读取</span></span><br><span class="line"><span class="keyword">while</span> (readOnlyBuffer.hasRemaining())&#123;</span><br><span class="line">    System.out.println(readOnlyBuffer.get());</span><br><span class="line">&#125;</span><br><span class="line">readOnlyBuffer.put((<span class="keyword">byte</span>)<span class="number">100</span>); <span class="comment">//会抛出 ReadOnlyBufferException</span></span><br></pre></td></tr></table></figure>

<p><strong>demo03: <code>MappedByteBuffer</code>可以让文件直接在内存（堆外内存）中进行修改，而如何同步到文件由NIO来完成</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">* 1、MappedByteBuffer可以让文件直接在内存中（堆外内存）修改，操作系统不需要拷贝一次</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">test</span><span class="params">()</span> <span class="keyword">throws</span> IOException </span>&#123;</span><br><span class="line">    RandomAccessFile randomAccessFile = <span class="keyword">new</span> RandomAccessFile(<span class="string">"1.txt"</span>, <span class="string">"rw"</span>);</span><br><span class="line">    <span class="comment">//获取对应的文件通道</span></span><br><span class="line">    FileChannel channel = randomAccessFile.getChannel();</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">    * 参数1: FileChannel.MapMode.READ_WRITE，使用的读写模式</span></span><br><span class="line"><span class="comment">    * 参数2: 0，可以直接修改的起始位置</span></span><br><span class="line"><span class="comment">    * 参数3: 5，是映射到内存的大小(不是文件中字母的索引位置），即将 1.txt 的多少个字节映射到内存，也就是可以直接修改的范围就是 [0, 5)</span></span><br><span class="line"><span class="comment">    * 实际的实例化类型：DirectByteBuffer</span></span><br><span class="line"><span class="comment">    */</span></span><br><span class="line">    MappedByteBuffer mappedByteBuffer = channel.map(FileChannel.MapMode.READ_WRITE, <span class="number">0</span>, <span class="number">5</span>);</span><br><span class="line"></span><br><span class="line">    mappedByteBuffer.put(<span class="number">0</span>,(<span class="keyword">byte</span>)<span class="string">'N'</span>);</span><br><span class="line">    mappedByteBuffer.put(<span class="number">3</span>, (<span class="keyword">byte</span>)<span class="string">'M'</span>);</span><br><span class="line">    mappedByteBuffer.put(<span class="number">5</span>, (<span class="keyword">byte</span>)<span class="string">'Y'</span>); <span class="comment">//会抛出 IndexOutOfBoundsException</span></span><br><span class="line"></span><br><span class="line">    randomAccessFile.close();</span><br><span class="line">    System.out.println(<span class="string">"修改成功~"</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h2 id="3-通道（Channel）"><a href="#3-通道（Channel）" class="headerlink" title="3. 通道（Channel）"></a>3. 通道（Channel）</h2><h3 id="3-1-基本介绍"><a href="#3-1-基本介绍" class="headerlink" title="3.1 基本介绍"></a>3.1 基本介绍</h3><p>NIO的通道类似于流，但有些区别</p>
<ul>
<li>通道可以同时进行读写，而流只能读或者只能写</li>
<li>通道可以实现异步读写数据</li>
<li>通道可以从缓存读数据，也可以写数据到缓存</li>
</ul>
<p><img src="http://zhuuu-bucket.oss-cn-beijing.aliyuncs.com/img/20201006/190004844.png" alt="mark"></p>
<ul>
<li>BIO 中的 <code>stream</code> 是单向的，例如：<code>FileInputStream</code>对象只能进行读取数据的操作，而NIO中的通道（Channel）是双向的，可以读操作，也可以写操作。</li>
<li><code>Channel</code> 在 NIO 中是一个接口：<code>public interface Channel extends Closeable{}</code></li>
<li>常用的<code>Channel</code>类有：<code>FileChannel</code>、<code>DatagramChannel</code>、<code>ServerSocketChannel</code>（类似<code>ServerSocket</code>）、<code>SocketChannel</code>（类似<code>Socket</code>）</li>
<li><code>FileChannel</code> 用于<strong>文件</strong>数据的读写，<code>DatagramChannel</code>用于<strong>UDP</strong>数据的读写，<code>ServerSocketChannel</code>和<code>SocketChannel</code>用于<strong>TCP</strong>数据读写</li>
</ul>
<p><strong>类关系图：</strong></p>
<p><img src="http://zhuuu-bucket.oss-cn-beijing.aliyuncs.com/img/20201006/190142717.png" alt="mark"></p>
<h3 id="3-2-FileChannel"><a href="#3-2-FileChannel" class="headerlink" title="3.2 FileChannel"></a>3.2 FileChannel</h3><ul>
<li><pre><code>public int read(ByteBuffer dst)
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">  - 从通道读取数据并放到缓冲区中</span><br><span class="line">  - 此操作也会移动 &#96;Buffer&#96; 中的&#96;position&#96;指针，不断往&#96;position&#96;中放数据，&#96;read&#96;完成后&#96;position&#96;指向&#96;limit&#96;。</span><br><span class="line"></span><br><span class="line">-</span><br></pre></td></tr></table></figure>
public int write(ByteBuffer src)
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">  - 把缓冲区的数据写到通道中</span><br><span class="line">  - 此操作也会不断移动&#96;Buffer&#96;中的&#96;position&#96;位置直到&#96;limit&#96;，读取到的数据就是&#96;position&#96;到&#96;limit&#96;这两个指针之间的数据。</span><br><span class="line"></span><br><span class="line">-</span><br></pre></td></tr></table></figure>
public long transferFrom(ReadableByteChannel src, long position, long count)
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">  - 从目标通道中复制数据到当前通道</span><br><span class="line"></span><br><span class="line">-</span><br></pre></td></tr></table></figure>
public long transferTo(long position, long count, WritableByteChannel target)
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">  - 把数据从当前通道复制给目标通道</span><br><span class="line">  - 该方法拷贝数据使用了**零拷贝**，通常用来在网络&#96;IO&#96;传输中，将&#96;FileChannel&#96;里面的文件数据直接拷贝到与客户端或者服务端连接的&#96;Channel&#96;里面从而达到文件传输。</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">**应用实例**</span><br><span class="line"></span><br><span class="line">**实例1：将数据写入到本地文件**</span><br><span class="line"></span><br><span class="line">&#96;&#96;&#96;java</span><br><span class="line">String str &#x3D; &quot;hello,尚硅谷&quot;;</span><br><span class="line">&#x2F;&#x2F;创建一个输出流 -&gt; Channel</span><br><span class="line">FileOutputStream fileOutputStream &#x3D; new FileOutputStream(&quot;d:\\file01.txt&quot;);</span><br><span class="line"></span><br><span class="line">&#x2F;&#x2F;通过 FileOutputStream 获取对应的 FileChannel</span><br><span class="line">&#x2F;&#x2F;这个 FileChannel 真实类型是 FileChannelImpl</span><br><span class="line">FileChannel fileChannel &#x3D; fileOutputStream.getChannel();</span><br><span class="line"></span><br><span class="line">&#x2F;&#x2F;创建一个缓冲区 ByteBuffer</span><br><span class="line">ByteBuffer byteBuffer &#x3D; ByteBuffer.allocate(1024);</span><br><span class="line">&#x2F;&#x2F;将str放入ByteBuffer</span><br><span class="line">byteBuffer.put(str.getBytes());</span><br><span class="line">&#x2F;&#x2F;对ByteBuffer进行反转，开始读取</span><br><span class="line">byteBuffer.flip();</span><br><span class="line">&#x2F;&#x2F;将ByteBuffer数据写入到FileChannel</span><br><span class="line">&#x2F;&#x2F;此操作会不断移动 Buffer中的 position到 limit 的位置</span><br><span class="line">fileChannel.write(byteBuffer);</span><br><span class="line">fileOutputStream.close();</span><br></pre></td></tr></table></figure>


</code></pre></li>
</ul>
<p><strong>实例2：从本地文件读取数据</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//创建文件的输入流</span></span><br><span class="line">File file = <span class="keyword">new</span> File(<span class="string">"d:\\file01.txt"</span>);</span><br><span class="line">FileInputStream fileInputStream = <span class="keyword">new</span> FileInputStream(file);</span><br><span class="line"><span class="comment">//通过fileInputStream 获取对应的FileChannel -&gt; 实际类型 FileChannelImpl</span></span><br><span class="line">FileChannel fileChannel = fileInputStream.getChannel();</span><br><span class="line"><span class="comment">//创建缓冲区</span></span><br><span class="line">ByteBuffer byteBuffer = ByteBuffer.allocate((<span class="keyword">int</span>) file.length());</span><br><span class="line"><span class="comment">//将通道的数据读入到buffer</span></span><br><span class="line">fileChannel.read(byteBuffer);</span><br><span class="line"></span><br><span class="line"><span class="comment">//将ByteBuffer 的字节数据转成String</span></span><br><span class="line">System.out.println(<span class="keyword">new</span> String(byteBuffer.array()));</span><br><span class="line">fileInputStream.close();</span><br></pre></td></tr></table></figure>



<p>实例1、2的示例图：</p>
<p><img src="http://zhuuu-bucket.oss-cn-beijing.aliyuncs.com/img/20201006/190523361.png" alt="mark"></p>
<p><strong>实例3：使用一个Buffer完成文件的读取</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line">FileInputStream fileInputStream = <span class="keyword">new</span> FileInputStream(<span class="string">"1.txt"</span>);</span><br><span class="line">FileChannel fileChannel1 = fileInputStream.getChannel();</span><br><span class="line"></span><br><span class="line">FileOutputStream fileOutputStream = <span class="keyword">new</span> FileOutputStream(<span class="string">"2.txt"</span>);</span><br><span class="line">FileChannel fileChannel2 = fileOutputStream.getChannel();</span><br><span class="line"></span><br><span class="line">ByteBuffer byteBuffer = ByteBuffer.allocate(<span class="number">512</span>);</span><br><span class="line"><span class="keyword">while</span> (<span class="keyword">true</span>)&#123;</span><br><span class="line">    <span class="comment">//清空buffer，由于循环的最后执行了 write 操作，会将 position 移动到 limit 的位置</span></span><br><span class="line">    <span class="comment">//清空 Buffer的操作才为上一次的循环重置position的位置</span></span><br><span class="line">    <span class="comment">// 如果没有重置position，那么上次读取后，position和limit位置一样，读取后read的值永远为0</span></span><br><span class="line">    byteBuffer.clear();</span><br><span class="line">    <span class="comment">//将数据存入 ByteBuffer，它会基于 Buffer 此刻的 position 和 limit 的值，</span></span><br><span class="line">    <span class="comment">// 将数据放入position的位置，然后不断移动position直到其与limit相等；</span></span><br><span class="line">    <span class="keyword">int</span> read = fileChannel1.read(byteBuffer);</span><br><span class="line">    System.out.println(<span class="string">"read="</span> + read);</span><br><span class="line">    <span class="keyword">if</span> (read == -<span class="number">1</span>) &#123; <span class="comment">//表示读完</span></span><br><span class="line">        <span class="keyword">break</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//将buffer中的数据写入到 FileChannel02 ---- 2.txt</span></span><br><span class="line">    byteBuffer.flip();</span><br><span class="line">    fileChannel2.write(byteBuffer);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">//关闭相关的流</span></span><br><span class="line">fileInputStream.close();</span><br><span class="line">fileOutputStream.close();</span><br></pre></td></tr></table></figure>

<p><img src="http://zhuuu-bucket.oss-cn-beijing.aliyuncs.com/img/20201006/190859988.png" alt="mark"></p>
<p><strong>实例4：拷贝文件 transferFrom 方法</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//创建相关流</span></span><br><span class="line">FileInputStream fileInputStream = <span class="keyword">new</span> FileInputStream(<span class="string">"d:\\a.gif"</span>);</span><br><span class="line">FileOutputStream fileOutputStream = <span class="keyword">new</span> FileOutputStream(<span class="string">"d:\\a2.gif"</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">//获取各个流对应的FileChannel</span></span><br><span class="line">FileChannel source = fileInputStream.getChannel();</span><br><span class="line">FileChannel dest = fileOutputStream.getChannel();</span><br><span class="line"></span><br><span class="line"><span class="comment">//使用 transferForm 完成拷贝</span></span><br><span class="line">dest.transferFrom(source, <span class="number">0</span>, source.size());</span><br><span class="line"><span class="comment">//关闭相关的通道和流</span></span><br><span class="line">source.close();</span><br><span class="line">dest.close();</span><br><span class="line">fileInputStream.close();</span><br><span class="line">fileOutputStream.close();</span><br></pre></td></tr></table></figure>

<ul>
<li>实例4相当于封装了实例3</li>
</ul>
<h3 id="3-3-ServerSocketChannel-和-SocketChannel-类"><a href="#3-3-ServerSocketChannel-和-SocketChannel-类" class="headerlink" title="3.3 ServerSocketChannel 和 SocketChannel 类"></a>3.3 ServerSocketChannel 和 SocketChannel 类</h3><p><strong>ServerSocketChannel</strong>：主要用于在<strong>服务器监听新的客户端<code>Socket</code>连接</strong></p>
<ul>
<li><code>public static ServerSocketChannel open()</code><ul>
<li>得到一个 <code>ServerSocketChannel</code> 通道</li>
</ul>
</li>
<li><code>public final ServerSocketChannel bind(SocketAddress local)</code><ul>
<li>设置服务器监听端口</li>
</ul>
</li>
<li><code>public final SelectableChannel configureBlocking(boolean block)</code><ul>
<li>用于设置阻塞或非阻塞模式，取值 <code>false</code> 表示采用非阻塞模式</li>
<li>此方法位于 <code>ServerSocketChannel</code> 和 <code>SocketChannel</code>的共同父类<code>AbstractSelectableChannel</code>类中</li>
</ul>
</li>
<li><code>public abstract SocketChannel accept()</code><ul>
<li>接受一个连接，返回代表这个连接的通道对象</li>
</ul>
</li>
<li><code>public final SelectionKey register(Selector sel, int ops)</code><ul>
<li>将<code>Channel</code>注册到选择器并设置监听事件，也可以在绑定的同时注册多个事件，如下所示：</li>
<li><code>channel.register(selector,Selectionkey.OP_READ | Selectionkey.OP_CONNECT)</code></li>
</ul>
</li>
</ul>
<p><strong>SocketChannel</strong>：网络IO通道，<strong>具体负责进行读写操作</strong>。NIO把缓冲区的数据写入通道，或者把通道里的数据读到缓冲区</p>
<ul>
<li><code>public static SocketChannel open()</code><ul>
<li>得到一个<code>SocketChannel</code>通道</li>
</ul>
</li>
<li><code>public final SelectableChannel configureBlocking(boolean block)</code><ul>
<li>设置阻塞或非阻塞模式，取值 false表示采用非阻塞模式</li>
<li>此方法位于 <code>ServerSocketChannel</code> 和 <code>SocketChannel</code>的共同父类<code>AbstractSelectableChannel</code>类中</li>
</ul>
</li>
<li><code>public abstract boolean connect(SocketAddress remote)</code><ul>
<li>连接服务器</li>
</ul>
</li>
<li><code>public boolean finishConnect()</code><ul>
<li>如果上面的方法连接失败，接下来就要通过该方法完成连接操作</li>
</ul>
</li>
<li><code>public int write(ByteBuffer src)</code><ul>
<li>往通道里写数据</li>
<li>这里写入的是<code>buffer</code>里面<code>position</code>到<code>limit</code>这个之间的数据</li>
</ul>
</li>
<li><code>public int read(ByteBuffer dst)</code><ul>
<li>从通道里读数据</li>
</ul>
</li>
<li><code>public final SelectionKey register(Selector sel, int ops, Object att)</code><ul>
<li>注册<code>Channel</code>到选择器并设置监听事件，最后一个参数可以设置共享数据</li>
</ul>
</li>
<li><code>public final void close()</code><ul>
<li>关闭通道</li>
</ul>
</li>
</ul>
<p><strong>应用实例</strong></p>
<ul>
<li>通过<code>Buffer</code>数组来完成读写操作，即<code>Scattering</code>和<code>Gathering</code></li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">* Scattering：将数据写入到buffer时，可以采用buffer数组，初次写入 【分散】</span></span><br><span class="line"><span class="comment">* Gathering：从buffer读取数据时，也可以采用buffer数组，依次读</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">test</span><span class="params">()</span> <span class="keyword">throws</span> IOException </span>&#123;</span><br><span class="line">    <span class="comment">//使用 ServerSocketChannel 和 SocketChannel</span></span><br><span class="line">    ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();</span><br><span class="line">    InetSocketAddress inetSocketAddress = <span class="keyword">new</span> InetSocketAddress(<span class="number">7000</span>);</span><br><span class="line">    <span class="comment">//绑定端口到socket，并启动</span></span><br><span class="line">    serverSocketChannel.socket().bind(inetSocketAddress);</span><br><span class="line">    <span class="comment">//创建一个Buffer数组</span></span><br><span class="line">    ByteBuffer[] byteBuffers = <span class="keyword">new</span> ByteBuffer[<span class="number">2</span>];</span><br><span class="line">    byteBuffers[<span class="number">0</span>] = ByteBuffer.allocate(<span class="number">5</span>);</span><br><span class="line">    byteBuffers[<span class="number">1</span>] = ByteBuffer.allocate(<span class="number">3</span>);</span><br><span class="line"></span><br><span class="line">    <span class="comment">//等待客户端的连接（Telnet）</span></span><br><span class="line">    SocketChannel socketChannel = serverSocketChannel.accept();</span><br><span class="line">    <span class="keyword">int</span> msgLength = <span class="number">8</span>; <span class="comment">//假定从客户端接受8个字节</span></span><br><span class="line">    <span class="comment">//循环的读取</span></span><br><span class="line">    <span class="keyword">while</span> (<span class="keyword">true</span>) &#123;</span><br><span class="line">        <span class="keyword">int</span> byteRead = <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">while</span> (byteRead &lt; msgLength) &#123;</span><br><span class="line">            <span class="keyword">long</span> l = socketChannel.read(byteBuffers);</span><br><span class="line">            byteRead += l; <span class="comment">//累计读取的字节数</span></span><br><span class="line">            System.out.println(<span class="string">"byteRead= "</span> + byteRead);</span><br><span class="line">            <span class="comment">//使用流打印，看看当前这个buffer的position和limit</span></span><br><span class="line">            Arrays.stream(byteBuffers)</span><br><span class="line">                .map(buffer -&gt; <span class="string">"position="</span> + buffer.position() + <span class="string">", limit = "</span> + buffer.limit())</span><br><span class="line">                .forEach(System.out::println);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">//读书数据后需要将所有的buffer进行flip</span></span><br><span class="line">        Arrays.asList(byteBuffers).forEach(Buffer::flip);</span><br><span class="line"></span><br><span class="line">        <span class="comment">//将数据读出显示到客户端</span></span><br><span class="line">        <span class="keyword">long</span> byteWrite = <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">while</span> (byteWrite &lt; msgLength) &#123;</span><br><span class="line">            <span class="keyword">long</span> l = socketChannel.write(byteBuffers);</span><br><span class="line">            byteWrite += l;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">//将所有的 buffer 进行clear操作</span></span><br><span class="line">        Arrays.asList(byteBuffers).forEach(Buffer::clear);</span><br><span class="line">        System.out.println(<span class="string">"byteRead="</span> + byteRead + <span class="string">", byteWrite="</span> + byteWrite</span><br><span class="line">                           + <span class="string">", msgLength="</span> + msgLength);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h2 id="4-Selector-选择器"><a href="#4-Selector-选择器" class="headerlink" title="4. Selector(选择器)"></a>4. Selector(选择器)</h2><h3 id="4-1-基本介绍"><a href="#4-1-基本介绍" class="headerlink" title="4.1 基本介绍"></a>4.1 基本介绍</h3><ul>
<li>Java 的 NIO，用非阻塞的 IO 方式。可以用一个线程，处理多个的客户端连接，就会使用到<strong>Selector</strong>(选择器)</li>
<li><code>Selector</code>能够检测多个注册的通道上是否有事件发生(注意:多个Channel以<strong>事件</strong>的方式可以注册到同一个<code>Selector</code>)，如果有事件发生，便获取事件然后针对每个事件进行相应的处理。这样就可以只用一个单线程去管理多个通道，也就是管理多个连接和请求。</li>
<li>只有在 连接/通道 真正有读写事件发生时，才会进行读写，就大大地减少了系统开销，并且不必为每个连接都创建一个线程，不用去维护多个线程</li>
<li>避免了多线程之间的上下文切换导致的开销</li>
</ul>
<p><img src="http://zhuuu-bucket.oss-cn-beijing.aliyuncs.com/img/20201007/202710545.png" alt="mark"></p>
<p>其中 : <code>Netty</code>的<code>IO</code>线程<code>NioEventLoop</code>聚合了<code>Selector</code>（选择器，也叫多路复用器），可以同时并发处理成百上千个客户端连接。</p>
<ul>
<li><p>当线程从某客户端 <code>Socket</code> 通道进行读写数据时，若没有数据可用时，该线程可以进行其他任务。</p>
</li>
<li><p>线程通常将非阻塞 <code>IO</code> 的空闲时间用于在其他通道上执行 <code>IO</code> 操作，所以单独的线程可以管理多个输入和输出通道。</p>
</li>
<li><p>由于读写操作都是非阻塞的，这就可以充分提升 <code>IO</code> 线程的运行效率，避免由于频繁 <code>I/O</code> 阻塞导致的线程挂起。</p>
</li>
<li><p>一个 <code>I/O</code> 线程可以并发处理 <code>N</code> 个客户端连接和读写操作，这从根本上解决了传统同步阻塞 <code>I/O</code> 一连接一线程模型，架构的性能、弹性伸缩能力和可靠性都得到了极大的提升。</p>
</li>
</ul>
<h3 id="4-2-SelectionKey-介绍"><a href="#4-2-SelectionKey-介绍" class="headerlink" title="4.2 SelectionKey 介绍"></a>4.2 SelectionKey 介绍</h3><p><img src="http://zhuuu-bucket.oss-cn-beijing.aliyuncs.com/img/20201007/203002487.png" alt="mark"></p>
<ul>
<li><code>Selector</code>通过管理<code>SelectionKey</code>的集合从而去监听各个<code>Channel</code>。</li>
<li>当<code>Channel</code>注册到<code>Selector</code>上面时，会携带该<code>Channel</code>关注的事件<strong>（SelectionKey包含Channel以及与之对应的事件）</strong>，并会返回一个<code>SelectionKey</code>的对象，<code>Selector</code>将该对象加入到它统一管理的集合中去，从而对<code>Channel</code>进行管理。</li>
<li><code>SelectionKey</code>表示的是<code>Selector</code>和网络通道的注册关系，故<code>FileChannel</code>是没有办法通过<code>SelectionKey</code>注册到<code>Selector</code>上去的。</li>
</ul>
<p><strong>四大事件</strong></p>
<ul>
<li><code>public static final int OP_READ = 1 &lt;&lt; 0</code><ul>
<li>值为<code>1</code>，表示读操作，</li>
<li>代表本<code>Channel</code>已经接受到其他客户端传过来的消息，需要将<code>Channel</code>中的数据读取到<code>Buffer</code>中去</li>
</ul>
</li>
<li><code>public static final int OP_WRITE = 1 &lt;&lt; 2</code><ul>
<li>值为<code>4</code>，表示写操作</li>
<li>一般临时将<code>Channel</code>的事件修改为它，在处理完后又修改回去。</li>
</ul>
</li>
<li><code>public static final int OP_CONNECT = 1 &lt;&lt; 3</code><ul>
<li>值为<code>8</code>，代表建立连接。</li>
<li>一般在<code>ServerSocketChannel</code>上绑定该事件，结合 <code>channel.finishConnect()</code>在连接建立异常时进行异常处理</li>
</ul>
</li>
<li><code>public static final int OP_ACCEPT = 1 &lt;&lt; 4</code><ul>
<li>值为<code>16</code>，表示由新的网络连接可以<code>accept</code>。</li>
<li>与<code>ServerSocketChannel</code>进行绑定，用于创建新的<code>SocketChannel</code>，并把其注册到<code>Selector</code>上去</li>
</ul>
</li>
</ul>
<p><strong>相关方法</strong></p>
<ul>
<li><code>public abstract Selector selector()</code><ul>
<li>得到该<code>SelectionKey</code>具体是属于哪个<code>Selector</code>对象的</li>
</ul>
</li>
<li><code>public abstract SelectableChannel channel()</code><ul>
<li>通过<code>SelectionKey</code>的到对应的<code>Channel</code></li>
</ul>
</li>
<li><code>public final Object attachment()</code><ul>
<li>得到与之关联的共享数据，一般用于获取<code>buffer</code></li>
<li>在使用<code>register</code>注册通道时，也可以为该<code>Channel</code>绑定一个<code>Buffer</code>，可以通过本方法获取这个<code>Buffer</code>。</li>
<li>通过<code>selectionKey.attach(Object ob)</code>绑定的数据，也是通过该方法获取</li>
</ul>
</li>
<li><code>public abstract SelectionKey interestOps()</code><ul>
<li>获取该<code>SelectionKey</code>下面的事件</li>
</ul>
</li>
<li><code>public abstract SelectionKey interestOps(int ops)</code><ul>
<li>用于设置或改变某个<code>Channel</code>关联的事件</li>
<li>增加事件：<code>key.interestOps(key.interestOps | SelectionKey.OP_WRITE)</code></li>
<li>减少事件：<code>key.interestOps(key.interestOps &amp; ~SelectionKey.OP_WRITE)</code></li>
</ul>
</li>
<li><code>public final boolean isAcceptable(),isReadable(),isWritable(),isConnectable()</code><ul>
<li>用于判断这个<code>SelectionKey</code>产生的是什么事件，与上面的事件类型一一对应</li>
</ul>
</li>
</ul>
<h3 id="4-3-Selector-常见方法"><a href="#4-3-Selector-常见方法" class="headerlink" title="4.3 Selector 常见方法"></a>4.3 Selector 常见方法</h3><ul>
<li><code>public static Selector open();</code><ul>
<li>得到一个选择器对象，实例化出 <code>WindowsSelectorImpl</code>对象。</li>
</ul>
</li>
<li><code>public int select(long timeout)</code><ul>
<li>监控所有注册的通道，当其中有<code>IO</code>操作可以进行时，将对应的<code>SelectionKey</code>加入到内部集合中并返回，返回的结果为<code>Channel</code>响应的事件总和，当结果为<code>0</code>时，表示本<code>Selector</code>监听的所有<code>Channel</code>中没有<code>Channel</code>产生事件。</li>
<li>如果不传入<code>timeout</code>值，就会阻塞线程，传入值则为阻塞多少毫秒，通过它设置超时时间。</li>
<li>之所以需要传入时间，是为了让它等待几秒钟再看有没有<code>Channel</code>会产生事件，从而获取一段时间内产生事件的<code>Channel</code>的总集合再一起处理。</li>
</ul>
</li>
<li><code>selector.selectNow();</code><ul>
<li>不会阻塞，立马返回冒泡的事件数</li>
</ul>
</li>
<li><code>public Set&lt;SelectionKey&gt; selectedKeys()</code><ul>
<li>从内部集合中得到所有的<code>SelectionKey</code></li>
</ul>
</li>
</ul>
<h3 id="4-4-Demo-实例"><a href="#4-4-Demo-实例" class="headerlink" title="4.4 Demo 实例"></a>4.4 Demo 实例</h3><p><strong>编码步骤</strong></p>
<ol>
<li>当客户端连接时，会通过<code>ServerSocketChannel</code> 得到 <code>SocketChannel</code></li>
<li><code>Selector</code> 进行监听 <code>select</code>方法, 返回有事件发生的通道的个数.</li>
<li>将<code>socketChannel</code>注册到<code>Selector</code>上, register(Selector sel, <strong>int</strong> ops), 一个<code>selector</code>上可以注册多个<code>SocketChannel</code></li>
<li>注册后返回一个 <code>SelectionKey</code>, 会和该<code>Selector</code> 关联(集合)</li>
<li>进一步得到各个 <code>SelectionKey</code> (有事件发生)</li>
<li>在通过 <code>SelectionKey</code> 反向获取 <code>SocketChannel</code> , 方法 <code>channel()</code></li>
<li>判断该<code>Channel</code>的事件类型，对不同事件进行不同的业务处理</li>
</ol>
<p><strong>NIO入门案例：实现服务器和客户端的简单通讯</strong></p>
<ul>
<li><strong>服务器端</strong></li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">Server</span><span class="params">()</span> <span class="keyword">throws</span> IOException </span>&#123;</span><br><span class="line">    <span class="comment">//创建ServerSocketChannel -&gt; ServerSocket</span></span><br><span class="line">    ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();</span><br><span class="line">    <span class="comment">//得到一个Selector对象</span></span><br><span class="line">    Selector selector = Selector.open();</span><br><span class="line">    <span class="comment">//绑定一个端口6666</span></span><br><span class="line">    serverSocketChannel.socket().bind(<span class="keyword">new</span> InetSocketAddress(<span class="number">6666</span>));</span><br><span class="line">    <span class="comment">//设置非阻塞</span></span><br><span class="line">    serverSocketChannel.configureBlocking(<span class="keyword">false</span>);</span><br><span class="line"></span><br><span class="line">    <span class="comment">//把 serverSocketChannel 注册到 selector ，关心事件为：OP_ACCEPT，有新的客户端连接</span></span><br><span class="line">    SelectionKey register = serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);</span><br><span class="line">    System.out.println();</span><br><span class="line">    <span class="comment">//循环等待客户端连接</span></span><br><span class="line">    <span class="keyword">while</span> (<span class="keyword">true</span>) &#123;</span><br><span class="line">        <span class="comment">//等待1秒，如果没有事件发生，就返回</span></span><br><span class="line">        <span class="keyword">if</span> (selector.select(<span class="number">1000</span>) == <span class="number">0</span>) &#123;</span><br><span class="line">            System.out.println(<span class="string">"服务器等待了1秒，无连接"</span>);</span><br><span class="line">            <span class="keyword">continue</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">//如果返回的 &gt; 0,表示已经获取到关注的事件</span></span><br><span class="line">        <span class="comment">// 就获取到相关的 selectionKey 集合，反向获取通道</span></span><br><span class="line">        Set&lt;SelectionKey&gt; selectionKeys = selector.selectedKeys();</span><br><span class="line"></span><br><span class="line">        <span class="comment">//遍历 Set&lt;SelectionKey&gt;，使用迭代器遍历</span></span><br><span class="line">        Iterator&lt;SelectionKey&gt; keyIterator = selectionKeys.iterator();</span><br><span class="line">        <span class="keyword">while</span> (keyIterator.hasNext()) &#123;</span><br><span class="line">            <span class="comment">//获取到SelectionKey</span></span><br><span class="line">            SelectionKey key = keyIterator.next();</span><br><span class="line">            <span class="comment">//根据 key 对应的通道发生的事件，做相应的处理</span></span><br><span class="line">            <span class="keyword">if</span> (key.isAcceptable()) &#123;<span class="comment">//如果是 OP_ACCEPT，有新的客户端连接</span></span><br><span class="line">                <span class="comment">//该客户端生成一个 SocketChannel</span></span><br><span class="line">                SocketChannel socketChannel = serverSocketChannel.accept();</span><br><span class="line">                System.out.println(<span class="string">"客户端连接成功，生成了一个SocketChannel："</span> + socketChannel.hashCode());</span><br><span class="line">                <span class="comment">//将SocketChannel设置为非阻塞</span></span><br><span class="line">                socketChannel.configureBlocking(<span class="keyword">false</span>);</span><br><span class="line">                <span class="comment">//将socketChannel注册到selector，关注事件为 OP_READ，同时给SocketChannel关联一个Buffer</span></span><br><span class="line">                socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(<span class="number">1024</span>));</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">if</span> (key.isReadable()) &#123;</span><br><span class="line">                <span class="comment">//通过key，反向获取到对应的Channel</span></span><br><span class="line">                SocketChannel channel = (SocketChannel) key.channel();</span><br><span class="line">                <span class="comment">//获取到该channel关联的Buffer</span></span><br><span class="line">                ByteBuffer buffer = (ByteBuffer) key.attachment();</span><br><span class="line">                channel.read(buffer);</span><br><span class="line">                System.out.println(<span class="string">"from 客户端："</span> + <span class="keyword">new</span> String(buffer.array()));</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="comment">//手动从集合中移除当前的 selectionKey，防止重复操作</span></span><br><span class="line">            keyIterator.remove();</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<ul>
<li><strong>客户端</strong></li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">Client</span><span class="params">()</span> <span class="keyword">throws</span> IOException </span>&#123;</span><br><span class="line">    <span class="comment">//得到一个网络通道</span></span><br><span class="line">    SocketChannel socketChannel = SocketChannel.open();</span><br><span class="line">    <span class="comment">//设置非阻塞</span></span><br><span class="line">    socketChannel.configureBlocking(<span class="keyword">false</span>);</span><br><span class="line">    <span class="comment">//提供服务器端的IP和端口</span></span><br><span class="line">    InetSocketAddress socketAddress = <span class="keyword">new</span> InetSocketAddress(<span class="string">"127.0.0.1"</span>, <span class="number">6666</span>);</span><br><span class="line">    <span class="comment">//连接服务器</span></span><br><span class="line">    <span class="keyword">if</span> (!socketChannel.connect(socketAddress))&#123; <span class="comment">//如果不成功</span></span><br><span class="line">        <span class="keyword">while</span> (!socketChannel.finishConnect())&#123;</span><br><span class="line">            System.out.println(<span class="string">"因为连接需要时间，客户端不会阻塞，可以做其他工作。。。"</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//如果连接成功，就发送数据</span></span><br><span class="line">    String str = <span class="string">"hello, 尚硅谷"</span>;</span><br><span class="line">    ByteBuffer byteBuffer = ByteBuffer.wrap(str.getBytes());</span><br><span class="line">    <span class="comment">//发送数据,实际上就是将buffer数据写入到channel</span></span><br><span class="line">    socketChannel.write(byteBuffer);</span><br><span class="line">    System.in.read();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h2 id="5-NIO-实战-群聊系统demo"><a href="#5-NIO-实战-群聊系统demo" class="headerlink" title="5. NIO 实战(群聊系统demo)"></a>5. NIO 实战(群聊系统demo)</h2><ul>
<li>需要实现客户端和服务器端之间的数据通讯，服务端能够将数据转发给其他所有客户端。</li>
</ul>
<h3 id="5-1-服务端"><a href="#5-1-服务端" class="headerlink" title="5.1 服务端"></a>5.1 服务端</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> GroupChat;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.io.IOException;</span><br><span class="line"><span class="keyword">import</span> java.net.InetSocketAddress;</span><br><span class="line"><span class="keyword">import</span> java.nio.ByteBuffer;</span><br><span class="line"><span class="keyword">import</span> java.nio.channels.*;</span><br><span class="line"><span class="keyword">import</span> java.util.Iterator;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">GroupChatServer</span> </span>&#123;</span><br><span class="line">    <span class="comment">// 属性</span></span><br><span class="line">    <span class="keyword">private</span> Selector selector;</span><br><span class="line">    <span class="keyword">private</span> ServerSocketChannel listenChannel;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> PORT = <span class="number">6677</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 初始化工作</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">GroupChatServer</span><span class="params">()</span></span>&#123;</span><br><span class="line">        <span class="keyword">try</span>&#123;</span><br><span class="line">            <span class="comment">// 打开连接通道</span></span><br><span class="line">            selector = Selector.open();</span><br><span class="line">            listenChannel = ServerSocketChannel.open();</span><br><span class="line"></span><br><span class="line">            <span class="comment">// 绑定端口</span></span><br><span class="line">            listenChannel.socket().bind(<span class="keyword">new</span> InetSocketAddress(PORT));</span><br><span class="line">            <span class="comment">// 设置非阻塞</span></span><br><span class="line">            listenChannel.configureBlocking(<span class="keyword">false</span>);</span><br><span class="line">            <span class="comment">// 注册到选择器上</span></span><br><span class="line">            listenChannel.register(selector, SelectionKey.OP_ACCEPT);</span><br><span class="line">        &#125;<span class="keyword">catch</span> (Exception e)&#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="comment">// 监听函数</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">listen</span><span class="params">()</span></span>&#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="keyword">while</span> (<span class="keyword">true</span>)&#123;</span><br><span class="line">                <span class="keyword">int</span> count = selector.select(<span class="number">2000</span>);</span><br><span class="line">                <span class="keyword">if</span>(count &gt; <span class="number">0</span>)&#123; <span class="comment">// 返回就绪的socket的数量</span></span><br><span class="line">                    <span class="comment">// 遍历得到SelectionKey集合</span></span><br><span class="line">                    Iterator&lt;SelectionKey&gt; iterator = selector.selectedKeys().iterator();</span><br><span class="line"></span><br><span class="line">                    <span class="comment">// 获取key</span></span><br><span class="line">                    SelectionKey key = iterator.next();</span><br><span class="line"></span><br><span class="line">                    <span class="comment">// 反向获取accept的事件的channel</span></span><br><span class="line">                    <span class="keyword">if</span> (key.isAcceptable())&#123;</span><br><span class="line">                        SocketChannel socketChannel = listenChannel.accept();</span><br><span class="line">                        socketChannel.configureBlocking(<span class="keyword">false</span>);</span><br><span class="line"></span><br><span class="line">                        <span class="comment">// 将socketChannel 注册到选择器上</span></span><br><span class="line">                        socketChannel.register(selector,SelectionKey.OP_READ);</span><br><span class="line"></span><br><span class="line">                        <span class="comment">// 提示客户端上线了</span></span><br><span class="line">                        System.out.println(socketChannel.getRemoteAddress().toString().substring(<span class="number">1</span>) + <span class="string">"上线了"</span>);</span><br><span class="line">                    &#125;</span><br><span class="line"></span><br><span class="line">                    <span class="keyword">if</span> (key.isReadable())&#123; <span class="comment">// 通道数据为可读的状态</span></span><br><span class="line">                        <span class="comment">// 读取数据</span></span><br><span class="line">                        readData(key);</span><br><span class="line">                    &#125;</span><br><span class="line"></span><br><span class="line">                    <span class="comment">// 删除当前的key,防止重复处理</span></span><br><span class="line">                    iterator.remove();</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;<span class="keyword">catch</span> (Exception e2)&#123;</span><br><span class="line">            e2.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="comment">// 读取客户端发送的数据</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">readData</span><span class="params">(SelectionKey key)</span></span>&#123;</span><br><span class="line">        <span class="comment">// 定义一个socketChannel</span></span><br><span class="line">        SocketChannel channel = <span class="keyword">null</span>;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">try</span>&#123;</span><br><span class="line">            <span class="comment">// 得到channel</span></span><br><span class="line">            channel = (SocketChannel) key.channel();</span><br><span class="line"></span><br><span class="line">            <span class="comment">// 创建buffer</span></span><br><span class="line">            ByteBuffer byteBuffer = ByteBuffer.allocate(<span class="number">1024</span>);</span><br><span class="line">            <span class="keyword">int</span> read = channel.read(byteBuffer);</span><br><span class="line"></span><br><span class="line">            <span class="keyword">if</span>(read &gt; <span class="number">0</span>)&#123;</span><br><span class="line">                <span class="comment">// 缓存区的数据转存到字符串</span></span><br><span class="line">                String msg = <span class="keyword">new</span> String(byteBuffer.array());</span><br><span class="line">                <span class="comment">// 输出消息</span></span><br><span class="line">                System.out.println(<span class="string">"来自客户端的消息"</span> + msg);</span><br><span class="line"></span><br><span class="line">                <span class="comment">// 将服务器消息转发给其他用户</span></span><br><span class="line">                sendToOthers(msg,channel);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;<span class="keyword">catch</span> (IOException e)&#123; <span class="comment">// 表示客户端此时下线了</span></span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                System.out.println(channel.getRemoteAddress() + <span class="string">"离线了...."</span>);</span><br><span class="line">                <span class="comment">//取消注册</span></span><br><span class="line">                key.cancel();</span><br><span class="line">                <span class="comment">//关闭通道</span></span><br><span class="line">                channel.close();</span><br><span class="line">            &#125;<span class="keyword">catch</span> (IOException e2)&#123;</span><br><span class="line">                e2.printStackTrace();</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 消息转发给其他用户</span></span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">sendToOthers</span><span class="params">(String msg,SocketChannel self)</span> <span class="keyword">throws</span> IOException </span>&#123;</span><br><span class="line">        System.out.println(<span class="string">"服务器消息转发中..."</span>);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 遍历已经注册到selector 的 socketchannel，排除掉自己</span></span><br><span class="line">        <span class="keyword">for</span> (SelectionKey key : selector.keys()) &#123;</span><br><span class="line">            <span class="comment">// 通过key取出对应的SocketChannel</span></span><br><span class="line">            Channel channel = key.channel();</span><br><span class="line">            <span class="comment">// 排除自己</span></span><br><span class="line">            <span class="keyword">if</span>(channel <span class="keyword">instanceof</span> SocketChannel &amp; channel != self)&#123;</span><br><span class="line">                <span class="comment">// 转型</span></span><br><span class="line">                SocketChannel dest = (SocketChannel) channel;</span><br><span class="line">                <span class="comment">// 将msg放入buffer中</span></span><br><span class="line">                ByteBuffer byteBuffer = ByteBuffer.wrap(msg.getBytes());</span><br><span class="line">                <span class="comment">// 将buffer数据写入到通道</span></span><br><span class="line">                dest.write(byteBuffer);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="comment">// 服务器启动</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>&#123;</span><br><span class="line">        GroupChatServer groupChatServer = <span class="keyword">new</span> GroupChatServer();</span><br><span class="line">        groupChatServer.listen();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h3 id="5-2-客户端"><a href="#5-2-客户端" class="headerlink" title="5.2 客户端"></a>5.2 客户端</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> GroupChat;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.io.IOException;</span><br><span class="line"><span class="keyword">import</span> java.net.InetSocketAddress;</span><br><span class="line"><span class="keyword">import</span> java.nio.ByteBuffer;</span><br><span class="line"><span class="keyword">import</span> java.nio.channels.SelectionKey;</span><br><span class="line"><span class="keyword">import</span> java.nio.channels.Selector;</span><br><span class="line"><span class="keyword">import</span> java.nio.channels.SocketChannel;</span><br><span class="line"><span class="keyword">import</span> java.util.Iterator;</span><br><span class="line"><span class="keyword">import</span> java.util.Scanner;</span><br><span class="line"><span class="keyword">import</span> java.util.Set;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">GroupChatClient</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> SocketChannel socketChannel = <span class="keyword">null</span>;</span><br><span class="line">    <span class="keyword">private</span> Selector selector = <span class="keyword">null</span>;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">static</span> <span class="keyword">int</span> PORT = <span class="number">6677</span>;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">static</span> String HOST = <span class="string">"127.0.0.1"</span>;</span><br><span class="line">    <span class="keyword">private</span> String userName;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 初始化操作</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">GroupChatClient</span><span class="params">()</span> <span class="keyword">throws</span> IOException </span>&#123;</span><br><span class="line">        selector = <span class="keyword">this</span>.selector.open();</span><br><span class="line">        <span class="comment">//链接服务器</span></span><br><span class="line">        socketChannel = SocketChannel.open(<span class="keyword">new</span> InetSocketAddress(HOST, PORT));</span><br><span class="line">        <span class="comment">//设置非阻塞</span></span><br><span class="line">        socketChannel.configureBlocking(<span class="keyword">false</span>);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 注册到selector上</span></span><br><span class="line">        socketChannel.register(selector, SelectionKey.OP_READ);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 获取用户名</span></span><br><span class="line">        userName = socketChannel.getLocalAddress().toString().substring(<span class="number">1</span>);</span><br><span class="line">        System.out.println(userName + <span class="string">"  is ok...."</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//向服务器发送消息</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">sendInfo</span><span class="params">(String info)</span> </span>&#123;</span><br><span class="line">        info = userName + <span class="string">"说:"</span> + info;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            socketChannel.write(ByteBuffer.wrap(info.getBytes()));</span><br><span class="line">        &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 读取从服务器回显的消息</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">readInfo</span><span class="params">()</span></span>&#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="keyword">int</span> select = selector.select();</span><br><span class="line">            <span class="keyword">if</span> (select &gt; <span class="number">0</span>)&#123; <span class="comment">// 说明有可用的通道</span></span><br><span class="line">                <span class="comment">// 获取key集合</span></span><br><span class="line">                Set&lt;SelectionKey&gt; selectionKeys = selector.selectedKeys();</span><br><span class="line">                <span class="comment">// 迭代</span></span><br><span class="line">                Iterator&lt;SelectionKey&gt; iterator = selectionKeys.iterator();</span><br><span class="line">                <span class="keyword">while</span> (iterator.hasNext())&#123;</span><br><span class="line">                    SelectionKey key = iterator.next();</span><br><span class="line">                    <span class="keyword">if</span>(key.isReadable())&#123;</span><br><span class="line">                        <span class="comment">// 得到相关通道</span></span><br><span class="line">                        SocketChannel  channel = (SocketChannel) key.channel();</span><br><span class="line">                        <span class="comment">// 得到一个buffer</span></span><br><span class="line">                        ByteBuffer byteBuffer = ByteBuffer.allocate(<span class="number">1024</span>);</span><br><span class="line">                        <span class="comment">// 读取数据</span></span><br><span class="line">                        channel.read(byteBuffer);</span><br><span class="line">                        <span class="comment">// 将数据转换成字符串</span></span><br><span class="line">                        String s = <span class="keyword">new</span> String(byteBuffer.array());</span><br><span class="line">                        System.out.println(s.trim());</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="comment">// 读完了删除这个key</span></span><br><span class="line">                iterator.remove();</span><br><span class="line">            &#125;<span class="keyword">else</span>&#123;</span><br><span class="line">                <span class="comment">// 这里可以干点别的东西</span></span><br><span class="line">            &#125;</span><br><span class="line">        &#125;<span class="keyword">catch</span> (Exception e)&#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> IOException </span>&#123;</span><br><span class="line">        GroupChatClient groupChatClient = <span class="keyword">new</span> GroupChatClient();</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 开启一个线程读取数据</span></span><br><span class="line">        <span class="keyword">new</span> Thread(()-&gt;&#123;<span class="keyword">while</span> (<span class="keyword">true</span>)&#123;groupChatClient.readInfo();&#125;&#125;).start();</span><br><span class="line">        <span class="comment">// 发送数据</span></span><br><span class="line">        Scanner sc = <span class="keyword">new</span> Scanner(System.in);</span><br><span class="line">        <span class="keyword">while</span> (sc.hasNextLine())&#123;</span><br><span class="line">            String s= sc.nextLine();</span><br><span class="line">            groupChatClient.sendInfo(s);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p><strong>注意事项：</strong></p>
<ul>
<li><p>使用<code>int read = channel.read(buffer)</code></p>
<p><strong>读取数据时</strong>，读取的结果情况：</p>
<ul>
<li>当<code>read=-1</code>时，说明客户端的数据发送完毕，并且主动的关闭<code>socket</code>。所以这种情况下，服务器程序需要关闭<code>socketSocket</code>，并且取消<code>key</code>的注册。注意：这个时候继续使用<code>SocketChannel</code>进行读操作的话，就会抛出：==<strong>远程主机强迫关闭一个现有的连接</strong>==的IO异常</li>
<li>当<code>read = 0</code>时：<ul>
<li>某一时刻<code>SocketChannel</code>中当前没有数据可读。</li>
<li>客户端的数据发送完毕。</li>
<li><a href="https://blog.csdn.net/cao478208248/article/details/41648359" target="_blank" rel="noopener">详情见此博文</a></li>
<li>但是对于博文中的这一条，经过本人测试，这种情况下返回的是读取的数据的大小，而不是<code>0</code>：<strong><code>ByteBuffer</code>的<code>position</code>等于<code>limit</code>，这个时候也会返回<code>0</code></strong></li>
</ul>
</li>
</ul>
</li>
</ul>
<p><strong>参考博客</strong> : <a href="https://www.cnblogs.com/hyy9527/p/13059248.html" target="_blank" rel="noopener">https://www.cnblogs.com/hyy9527/p/13059248.html</a></p>
<p>​                    <a href="https://blog.csdn.net/qq_35751014/article/details/104411347" target="_blank" rel="noopener">https://blog.csdn.net/qq_35751014/article/details/104411347</a></p>

      
      <!-- reward -->
      
      <div id="reward-btn">
        打赏
      </div>
      
    </div>
      <!-- copyright -->
      
        <div class="declare">
          <ul class="post-copyright">
            <li>
              <i class="ri-copyright-line"></i>
              <strong>版权声明： </strong s>
              本博客所有文章除特别声明外，均采用 <a href="https://www.apache.org/licenses/LICENSE-2.0.html" rel="external nofollow"
                target="_blank">Apache License 2.0</a> 许可协议。转载请注明出处！
            </li>
          </ul>
        </div>
        
    <footer class="article-footer">
      
          
<div class="share-btn">
      <span class="share-sns share-outer">
        <i class="ri-share-forward-line"></i>
        分享
      </span>
      <div class="share-wrap">
        <i class="arrow"></i>
        <div class="share-icons">
          
          <a class="weibo share-sns" href="javascript:;" data-type="weibo">
            <i class="ri-weibo-fill"></i>
          </a>
          <a class="weixin share-sns wxFab" href="javascript:;" data-type="weixin">
            <i class="ri-wechat-fill"></i>
          </a>
          <a class="qq share-sns" href="javascript:;" data-type="qq">
            <i class="ri-qq-fill"></i>
          </a>
          <a class="douban share-sns" href="javascript:;" data-type="douban">
            <i class="ri-douban-line"></i>
          </a>
          <!-- <a class="qzone share-sns" href="javascript:;" data-type="qzone">
            <i class="icon icon-qzone"></i>
          </a> -->
          
          <a class="facebook share-sns" href="javascript:;" data-type="facebook">
            <i class="ri-facebook-circle-fill"></i>
          </a>
          <a class="twitter share-sns" href="javascript:;" data-type="twitter">
            <i class="ri-twitter-fill"></i>
          </a>
          <a class="google share-sns" href="javascript:;" data-type="google">
            <i class="ri-google-fill"></i>
          </a>
        </div>
      </div>
</div>

<div class="wx-share-modal">
    <a class="modal-close" href="javascript:;"><i class="ri-close-circle-line"></i></a>
    <p>扫一扫，分享到微信</p>
    <div class="wx-qrcode">
      <img src="//api.qrserver.com/v1/create-qr-code/?size=150x150&data=http://zhuuu.work/2020/10/01/Netty/Netty-02-NIO%E5%AE%9E%E6%88%98%E5%8F%8A%E5%8E%9F%E7%90%86/" alt="微信分享二维码">
    </div>
</div>

<div id="share-mask"></div>
      
      
  <ul class="article-tag-list" itemprop="keywords"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/NIO/" rel="tag">NIO</a></li><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/Netty/" rel="tag">Netty</a></li></ul>


    </footer>

  </div>

  
  
  <nav class="article-nav">
    
      <a href="/2020/10/01/LeetcodeExplore/%E8%82%A1%E7%A5%A8%E9%97%AE%E9%A2%98-%E5%90%88%E9%9B%86/" class="article-nav-link">
        <strong class="article-nav-caption">上一篇</strong>
        <div class="article-nav-title">
          
            股票问题-合集
          
        </div>
      </a>
    
    
      <a href="/2020/10/01/Netty/Netty-01-BIO%E5%AE%9E%E6%88%98%E5%8F%8A%E5%8E%9F%E7%90%86/" class="article-nav-link">
        <strong class="article-nav-caption">下一篇</strong>
        <div class="article-nav-title">Netty-01-BIO实战</div>
      </a>
    
  </nav>


  

  
  
<!-- valine评论 -->
<div id="vcomments-box">
    <div id="vcomments">
    </div>
</div>
<script src="//cdn1.lncld.net/static/js/3.0.4/av-min.js"></script>
<script src='https://cdn.jsdelivr.net/npm/valine@1.3.10/dist/Valine.min.js'></script>
<script>
    new Valine({
        el: '#vcomments',
        notify: false,
        verify: '',
        app_id: '',
        app_key: '',
        path: window.location.pathname,
        avatar: 'mp',
        placeholder: '给我的文章加点评论吧~',
        recordIP: true
    });
    const infoEle = document.querySelector('#vcomments .info');
    if (infoEle && infoEle.childNodes && infoEle.childNodes.length > 0) {
        infoEle.childNodes.forEach(function (item) {
            item.parentNode.removeChild(item);
        });
    }
</script>
<style>
    #vcomments-box {
        padding: 5px 30px;
    }

    @media screen and (max-width: 800px) {
        #vcomments-box {
            padding: 5px 0px;
        }
    }

    #vcomments-box #vcomments {
        background-color: #fff;
    }

    .v .vlist .vcard .vh {
        padding-right: 20px;
    }

    .v .vlist .vcard {
        padding-left: 10px;
    }
</style>

  

  
  
<div class="gitalk" id="gitalk-container"></div>

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/gitalk@1.5.0/dist/gitalk.css">


<script src="https://cdn.jsdelivr.net/npm/gitalk@1.5.0/dist/gitalk.min.js"></script>


<script src="https://cdn.jsdelivr.net/npm/blueimp-md5@2.10.0/js/md5.min.js"></script>

<script type="text/javascript">
  var gitalk = new Gitalk({
    clientID: 'db188ed8c86dc4b0dbf3',
    clientSecret: 'a58f92160e5a9efd726b7d533000a0737f3e3f3e',
    repo: 'Blog-comments',
    owner: 'Zhuuuuuuuu',
    admin: ['Zhuuuuuuuu'],
    // id: location.pathname,      // Ensure uniqueness and length less than 50
    id: md5(location.pathname),
    distractionFreeMode: false,  // Facebook-like distraction free mode
    pagerDirection: 'last'
  })

  gitalk.render('gitalk-container')
</script>

  

</article>
</section>
      <footer class="footer">
  <div class="outer">
    <ul class="list-inline">
      <li>
        &copy;
        2019-2021
        Zhuuu
      </li>
      <li>
        
      </li>
    </ul>
    <ul class="list-inline">
      <li>
        
        
        <span>
  <i>PV:<span id="busuanzi_value_page_pv"></span></i>
  <i>UV:<span id="busuanzi_value_site_uv"></span></i>
</span>
        
      </li>
      <li>
        <!-- cnzz统计 -->
        
        <script type="text/javascript" src='https://s9.cnzz.com/z_stat.php?id=1278069914&amp;web_id=1278069914'></script>
        
      </li>
    </ul>
  </div>
</footer>
    <div class="to_top">
        <div class="totop" id="totop">
  <i class="ri-arrow-up-line"></i>
</div>
      </div>
    </main>
      <aside class="sidebar">
        <button class="navbar-toggle"></button>
<nav class="navbar">
  
  <div class="logo">
    <a href="/"><img src="/images/ayer-side.svg" alt="朱酱酱的学习博客"></a>
  </div>
  
  <ul class="nav nav-main">
    
    <li class="nav-item">
      <a class="nav-item-link" href="/">主页</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/tags">标签</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/tags/JVM/">JVM</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/tags/JDK%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90/">JDK源码</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/tags/%E5%A4%9A%E7%BA%BF%E7%A8%8B/">多线程</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/tags/Mysql/">Mysql</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/tags/Redis/">Redis</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/tags/%E8%AE%BE%E8%AE%A1%E8%80%85%E6%A8%A1%E5%BC%8F/">设计模式</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/tags/MyBatis/">MyBatis</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/tags/SpringMVC/">SpringMVC</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/tags/Spring/">Spring</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/tags/SpringBoot/">SpringBoot</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/tags/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/">Linux</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/tags/Leetcode/">Leetcode</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/tags/%E5%89%8D%E7%AB%AF/">前端</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/tags/%E7%BD%91%E7%BB%9C%E7%BC%96%E7%A8%8B/">网络编程</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/tags/photoshop/">photoshop</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="http://smartzhuuu.lofter.com/" target="_blank" rel="noopener">摄影</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/2020/about">关于我</a>
    </li>
    
  </ul>
</nav>
<nav class="navbar navbar-bottom">
  <ul class="nav">
    <li class="nav-item">
      
      <a class="nav-item-link nav-item-search"  title="Search">
        <i class="ri-search-line"></i>
      </a>
      
      
      <a class="nav-item-link" target="_blank" href="/atom.xml" title="RSS Feed">
        <i class="ri-rss-line"></i>
      </a>
      
    </li>
  </ul>
</nav>
<div class="search-form-wrap">
  <div class="local-search local-search-plugin">
  <input type="search" id="local-search-input" class="local-search-input" placeholder="Search...">
  <div id="local-search-result" class="local-search-result"></div>
</div>
</div>
      </aside>
      <div id="mask"></div>

<!-- #reward -->
<div id="reward">
  <span class="close"><i class="ri-close-line"></i></span>
  <p class="reward-p"><i class="ri-cup-line"></i>请我喝杯咖啡吧~</p>
  <div class="reward-box">
    
    <div class="reward-item">
      <img class="reward-img" src="/images/alipay.jpg">
      <span class="reward-type">支付宝</span>
    </div>
    
    
    <div class="reward-item">
      <img class="reward-img" src="/images/wechat.jpg">
      <span class="reward-type">微信</span>
    </div>
    
  </div>
</div>
      
<script src="/js/jquery-2.0.3.min.js"></script>


<script src="/js/jquery.justifiedGallery.min.js"></script>


<script src="/js/lazyload.min.js"></script>


<script src="/js/busuanzi-2.3.pure.min.js"></script>


<script src="/js/share.js"></script>



<script src="/fancybox/jquery.fancybox.min.js"></script>




<script>
  try {
    var typed = new Typed("#subtitle", {
    strings: ['昨夜西风凋碧树。独上高楼，望尽天涯路','衣带渐宽终不悔，为伊消得人憔悴。','众里寻他千百度。蓦然回首，那人却在，灯火阑珊处。'],
    startDelay: 0,
    typeSpeed: 200,
    loop: true,
    backSpeed: 100,
    showCursor: true
    });
  } catch (err) {
  }
  
</script>




<script src="/js/tocbot.min.js"></script>

<script>
  // Tocbot_v4.7.0  http://tscanlin.github.io/tocbot/
  tocbot.init({
    tocSelector: '.tocbot',
    contentSelector: '.article-entry',
    headingSelector: 'h1, h2, h3, h4, h5, h6',
    hasInnerContainers: true,
    scrollSmooth: true,
    scrollContainer:'main',
    positionFixedSelector: '.tocbot',
    positionFixedClass: 'is-position-fixed',
    fixedSidebarOffset: 'auto',
    onClick: (e) => {
      $('.toc-link').removeClass('is-active-link');
      $(`a[href=${e.target.hash}]`).addClass('is-active-link');
      $(e.target.hash).scrollIntoView();
      return false;
    }
  });
</script>


<script>
  var ayerConfig = {
    mathjax: true
  }
</script>


<script src="/js/ayer.js"></script>


<script src="https://cdn.jsdelivr.net/npm/jquery-modal@0.9.2/jquery.modal.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/jquery-modal@0.9.2/jquery.modal.min.css">


<!-- Root element of PhotoSwipe. Must have class pswp. -->
<div class="pswp" tabindex="-1" role="dialog" aria-hidden="true">

    <!-- Background of PhotoSwipe. 
         It's a separate element as animating opacity is faster than rgba(). -->
    <div class="pswp__bg"></div>

    <!-- Slides wrapper with overflow:hidden. -->
    <div class="pswp__scroll-wrap">

        <!-- Container that holds slides. 
            PhotoSwipe keeps only 3 of them in the DOM to save memory.
            Don't modify these 3 pswp__item elements, data is added later on. -->
        <div class="pswp__container">
            <div class="pswp__item"></div>
            <div class="pswp__item"></div>
            <div class="pswp__item"></div>
        </div>

        <!-- Default (PhotoSwipeUI_Default) interface on top of sliding area. Can be changed. -->
        <div class="pswp__ui pswp__ui--hidden">

            <div class="pswp__top-bar">

                <!--  Controls are self-explanatory. Order can be changed. -->

                <div class="pswp__counter"></div>

                <button class="pswp__button pswp__button--close" title="Close (Esc)"></button>

                <button class="pswp__button pswp__button--share" style="display:none" title="Share"></button>

                <button class="pswp__button pswp__button--fs" title="Toggle fullscreen"></button>

                <button class="pswp__button pswp__button--zoom" title="Zoom in/out"></button>

                <!-- Preloader demo http://codepen.io/dimsemenov/pen/yyBWoR -->
                <!-- element will get class pswp__preloader--active when preloader is running -->
                <div class="pswp__preloader">
                    <div class="pswp__preloader__icn">
                        <div class="pswp__preloader__cut">
                            <div class="pswp__preloader__donut"></div>
                        </div>
                    </div>
                </div>
            </div>

            <div class="pswp__share-modal pswp__share-modal--hidden pswp__single-tap">
                <div class="pswp__share-tooltip"></div>
            </div>

            <button class="pswp__button pswp__button--arrow--left" title="Previous (arrow left)">
            </button>

            <button class="pswp__button pswp__button--arrow--right" title="Next (arrow right)">
            </button>

            <div class="pswp__caption">
                <div class="pswp__caption__center"></div>
            </div>

        </div>

    </div>

</div>

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/default-skin/default-skin.css">
<script src="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe-ui-default.min.js"></script>

<script>
    function viewer_init() {
        let pswpElement = document.querySelectorAll('.pswp')[0];
        let $imgArr = document.querySelectorAll(('.article-entry img:not(.reward-img)'))

        $imgArr.forEach(($em, i) => {
            $em.onclick = () => {
                // slider展开状态
                // todo: 这样不好，后面改成状态
                if (document.querySelector('.left-col.show')) return
                let items = []
                $imgArr.forEach(($em2, i2) => {
                    let img = $em2.getAttribute('data-idx', i2)
                    let src = $em2.getAttribute('data-target') || $em2.getAttribute('src')
                    let title = $em2.getAttribute('alt')
                    // 获得原图尺寸
                    const image = new Image()
                    image.src = src
                    items.push({
                        src: src,
                        w: image.width || $em2.width,
                        h: image.height || $em2.height,
                        title: title
                    })
                })
                var gallery = new PhotoSwipe(pswpElement, PhotoSwipeUI_Default, items, {
                    index: parseInt(i)
                });
                gallery.init()
            }
        })
    }
    viewer_init()
</script>



<script type="text/x-mathjax-config">
  MathJax.Hub.Config({
      tex2jax: {
          inlineMath: [ ['$','$'], ["\\(","\\)"]  ],
          processEscapes: true,
          skipTags: ['script', 'noscript', 'style', 'textarea', 'pre', 'code']
      }
  });

  MathJax.Hub.Queue(function() {
      var all = MathJax.Hub.getAllJax(), i;
      for(i=0; i < all.length; i += 1) {
          all[i].SourceElement().parentNode.className += ' has-jax';
      }
  });
</script>

<script src="https://cdn.jsdelivr.net/npm/mathjax@2.7.6/unpacked/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>


<script type="text/javascript" src="https://js.users.51.la/20544303.js"></script>
  </div>
</body>

</html>